package com.asgard.crypto;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.DisplayText;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asgard.utils.ExtensionUtils;

/**
 * 该类封装了证书签发中心，封装了签发证书，验证证书，用证书私钥签名，用证书验证的相关函数。
 */
public class CertificateHelper {

    private static final Logger log = LoggerFactory.getLogger(CertificateHelper.class);

    static {
        // 为了让JVM加载BouncyCastle
        Elliptic.init();
    }

    /**
     * 自签证书，该证书一般用于Root CA。
     *
     * @param key       密钥
     * @param name      证书名称
     * @param memberID  用户MEMBER ID
     * @param sn        序列号
     * @param startDate 有效起始时间
     * @param endDate   有效截止时间
     * @return 自签的证书实例
     * @throws CertIOException           证书操作异常
     * @throws OperatorCreationException 证书操作异常
     * @throws CertificateException      证书操作异常
     */
    public static Certificate signForSelf(ECKey key, String name, String memberID, BigInteger sn, Date startDate,
            Date endDate) throws CertIOException, OperatorCreationException, CertificateException {
        return signForSelf(key.getPublicKey(), key.getPrivateKey(), name, memberID, sn, startDate, endDate);
    }

    /**
     * 自签证书，该证书一般用于Root CA。
     *
     * @param publicKey 公钥
     * @param privKey   私钥
     * @param name      证书名称
     * @param memberID  用户MEMBER ID
     * @param sn        序列号
     * @param startDate 有效起始时间
     * @param endDate   有效截止时间
     * @return 自签的证书实例
     * @throws CertIOException           证书操作异常
     * @throws OperatorCreationException 证书操作异常
     * @throws CertificateException      证书操作异常
     */
    public static Certificate signForSelf(PublicKey publicKey, PrivateKey privKey, String name, String memberID,
            BigInteger sn, Date startDate, Date endDate)
            throws CertIOException, OperatorCreationException, CertificateException {
        // Name字段
        X500Name issuerName = new X500Name(name);
        X500Name subjectName = new X500Name(name);
        X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuerName, sn, startDate, endDate,
                subjectName, publicKey);
        // Extension字段
        builder.addExtension(Certificate.ROLE, false, new DisplayText("ROOT"))
                .addExtension(Certificate.SUB_ROLE, false, new DisplayText("ROOT"))
                .addExtension(Certificate.MEMBER_ID, false, new DisplayText(memberID))
                .addExtension(Certificate.ISSUER_MEMBER_ID, false, new DisplayText(memberID))
                .addExtension(Certificate.ISSUER_SN, false, new DisplayText(sn.toString()));
        // KeyUsage字段
        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
        KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.keyEncipherment
                | KeyUsage.dataEncipherment | KeyUsage.cRLSign);
        builder.addExtension(Extension.keyUsage, false, usage);
        // KeyPurpose字段
        ASN1EncodableVector purposes = new ASN1EncodableVector();
        purposes.add(KeyPurposeId.id_kp_serverAuth);
        purposes.add(KeyPurposeId.id_kp_clientAuth);
        purposes.add(KeyPurposeId.anyExtendedKeyUsage);
        builder.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));
        // 生成证书
        ContentSigner signer = new JcaContentSignerBuilder("SHA256withECDSA")
                .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privKey);
        X509Certificate x509certificate = new JcaX509CertificateConverter()
                .setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(builder.build(signer));
        return new Certificate(x509certificate);
    }

    /**
     * 使用本地自有证书签发新证书。
     *
     * @param local     本地自有证书
     * @param csr       申请签名的证书
     * @param key       密钥
     * @param memberID  用户MEMBER ID
     * @param sn        证书序列号
     * @param role      角色
     * @param subRole   子角色
     * @param startDate 有效起始日期
     * @param endDate   有效截止日期
     * @return 签发的证书实例
     * @throws OperatorCreationException 证书操作异常
     * @throws CertificateException      证书操作异常
     * @throws IOException               证书操作异常
     */
    public static Certificate signNewCertificate(Certificate local, CertificationRequest csr, ECKey key,
            String memberID, BigInteger sn, String role, String subRole, Date startDate, Date endDate)
            throws OperatorCreationException, CertificateException, IOException {
        return signNewCertificate(local, csr, key.getPrivateKey(), memberID, sn, role, subRole, startDate, endDate);
    }

    /**
     * 使用本地自有证书签发新证书。
     *
     * @param local     本地自有证书
     * @param csr       申请签名的证书
     * @param privKey   私钥
     * @param memberID  用户ID
     * @param sn        证书序列号
     * @param role      角色
     * @param subRole   子角色
     * @param startDate 有效起始日期
     * @param endDate   有效截止日期
     * @return 签发的证书实例
     * @throws OperatorCreationException 证书操作异常
     * @throws CertificateException      证书操作异常
     * @throws IOException               证书操作异常
     */
    public static Certificate signNewCertificate(Certificate local, CertificationRequest csr, PrivateKey privKey,
            String memberID, BigInteger sn, String role, String subRole, Date startDate, Date endDate)
            throws OperatorCreationException, CertificateException, IOException {
        // Name字段
        X509Certificate localX509Certificate = local.getX509Cert();
        X500Name issuer = new X500Name(localX509Certificate.getSubjectDN().toString());
        X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuer, sn, startDate, endDate,
                csr.getSubject(), csr.getSubjectPublicKeyInfo());
        // Extension字段
        builder.addExtension(Certificate.MEMBER_ID, false, new DisplayText(memberID))
                .addExtension(Certificate.ROLE, false, new DisplayText(role))
                .addExtension(Certificate.SUB_ROLE, false, new DisplayText(subRole))
                .addExtension(Certificate.ISSUER_MEMBER_ID, false, new DisplayText(local.getMemberID()))
                .addExtension(Certificate.ISSUER_SN, false, new DisplayText(local.getSN().toString()));
        // KeyUsage字段
        KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign);
        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(3)).addExtension(Extension.keyUsage,
                true, keyUsage);
        // 签发证书
        ContentSigner signer = new JcaContentSignerBuilder("SHA256withECDSA").setProvider("BC").build(privKey);
        X509CertificateHolder holder = builder.build(signer);
        return new Certificate(holder.toASN1Structure().getEncoded());
    }

    /**
     * 生成CSR，即证书签发请求。
     *
     * @param key      ECC密钥
     * @param dn       使用者名称
     * @param role     角色（如：RootCA/连接证书/交易证书）
     * @param subRole  子角色（如：VP/NVP；存证/Token——用户/...）
     * @param memberID 用户ID
     * @return 生成的CSR实例
     * @throws OperatorCreationException 证书操作异常
     * @throws IOException               证书操作异常
     */
    public static CertificationRequest createCSR(ECKey key, String dn, String role, String subRole, String memberID)
            throws OperatorCreationException, IOException {
        return createCSR(key.getPublicKey(), key.getPrivateKey(), dn, role, subRole, memberID);
    }

    /**
     * 生成CSR，即证书签发请求。
     *
     * @param publicKey  公钥
     * @param privateKey 私钥
     * @param dn         证书DN信息
     * @param role       角色（如：RootCA/连接证书/交易证书）
     * @param subRole    子角色（如：VP/NVP；存证/Token——用户/...）
     * @param memberID   用户ID
     * @return 生成的CSR实例
     * @throws OperatorCreationException 证书操作异常
     * @throws IOException               证书操作异常
     */
    public static CertificationRequest createCSR(PublicKey publicKey, PrivateKey privateKey, String dn, String role,
            String subRole, String memberID) throws OperatorCreationException, IOException {
        X500Name principal = new X500Name(dn);
        PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(principal, publicKey);

        final ASN1ObjectIdentifier ROLE = ExtensionUtils.ROLE.intern();
        final ASN1ObjectIdentifier SUB_ROLE = ExtensionUtils.SUB_ROLE.intern();
        final ASN1ObjectIdentifier MEMBER_ID = ExtensionUtils.MEMBER_ID.intern();
        p10Builder.addAttribute(ROLE, new DisplayText(role));
        p10Builder.addAttribute(SUB_ROLE, new DisplayText(subRole));
        p10Builder.addAttribute(MEMBER_ID, new DisplayText(memberID));
        JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withECDSA");
        ContentSigner signer = csBuilder.build(privateKey);
        PKCS10CertificationRequest csr = p10Builder.build(signer);
        return new CertificationRequest(csr);
    }

    /**
     * 用证书校验签名。
     *
     * @param cert      证书
     * @param data      要校验的数据
     * @param signature 签名
     * @return 签名校验是否通过
     */
    public static boolean verify(Certificate cert, byte[] data, byte[] signature) {
        try {
            Signature sig = Signature.getInstance(cert.getSigAlgName(), BouncyCastleProvider.PROVIDER_NAME);
            sig.initVerify(cert.getX509Cert());
            sig.update(data);
            boolean result = sig.verify(signature);
            return result;
        } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
            log.error("不支持的签名算法: {}", cert.getSigAlgName());
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            log.error("证书格式错误: {}", e.getMessage());
            return false;
        } catch (SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 用证书对应的私钥签名。
     *
     * @param cert    证书
     * @param privKey 证书对应的私钥
     * @param data    要签名的数据
     * @return 签名
     */
    public static byte[] sign(Certificate cert, ECKey privKey, byte[] data) {
        return sign(cert, privKey.getPrivateKey(), data);
    }

    /**
     * 用证书对应的私钥签名。
     *
     * @param cert    证书
     * @param privKey 证书对应的私钥
     * @param data    要签名的数据
     * @return 签名
     */
    public static byte[] sign(Certificate cert, PrivateKey privKey, byte[] data) {
        try {
            Signature sig = Signature.getInstance(cert.getSigAlgName(), BouncyCastleProvider.PROVIDER_NAME);
            sig.initSign(privKey);
            sig.update(data);
            byte[] signature = sig.sign();
            return signature;
        } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
            log.error("不支持的签名算法: {}", cert.getSigAlgName());
            throw new RuntimeException(e);
        } catch (SignatureException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            log.error("证书或私钥格式错误: {}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 将证书保存至文件。
     *
     * @param cert 证书实例
     * @param file 保存文件路径
     * @throws IOException 输入输出异常
     */
    public static void saveCertificateToFile(Certificate cert, File file) throws IOException {
        try (PrintWriter w = new PrintWriter(file)) {
            w.println(cert.getBase64String());
        }
    }

    /**
     * 将CSR，即证书签发请求保存至文件。
     *
     * @param csr  证书签发请求
     * @param file 保存文件路径
     * @throws IOException 输入输出异常
     */
    public static void saveRequestToFile(CertificationRequest csr, File file) throws IOException {
        try (PrintWriter w = new PrintWriter(file)) {
            w.println(csr.getBase64String());
        }
    }

}
